# ColumnedRowIterator.java

    package org.codefilarete.stalactite.sql.result;

    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.LinkedHashSet;
    import java.util.Map;
    import java.util.Set;
    import javax.annotation.Nullable;

    import org.codefilarete.stalactite.query.api.Selectable;
    import org.codefilarete.stalactite.sql.statement.SQLStatement.BindingException;
    import org.codefilarete.stalactite.sql.statement.binder.ParameterBinderIndex;
    import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;

    /**
     * {@link ResultSetIterator} specialized in {@link ColumnedRow} building for each {@link ResultSet} line.
     *
     * @author Guillaume Mary
     */
    public class ColumnedRowIterator extends ResultSetIterator<ColumnedRow> {

    	/** Readers for each column of the {@link ResultSet}, by name (may contain double but doesn't matter, causes only extra conversion) */
    	private final Iterable<Decoder> decoders;

    	private final Map<Selectable<?>, String> aliases;

    	/**
    	 * Constructs an instance without {@link ResultSet} : it shall be set further with {@link #setResultSet(ResultSet)}.
    	 *
    	 * @param columnNameBinders columns and associated {@link ResultSetReader} to use for {@link ResultSet} reading
    	 */
    	public ColumnedRowIterator(Map<? extends Selectable<?>, ? extends ResultSetReader<?>> columnNameBinders,
    							   Map<? extends Selectable<?>, String> aliases) {
    		this(null, columnNameBinders, aliases);
    	}

    	/**
    	 * Constructs an instance that will iterate over the given {@link ResultSet}. It can be changed with {@link #setResultSet(ResultSet)}.
    	 *
    	 * @param rs a ResultSet to wrap into an {@link java.util.Iterator}
    	 * @param columnNameBinders column names and associated {@link ResultSetReader} to use for {@link ResultSet} reading
    	 */
    	public ColumnedRowIterator(@Nullable ResultSet rs,
    							   Map<? extends Selectable<?>, ? extends ResultSetReader<?>> columnNameBinders,
    							   Map<? extends Selectable<?>, String> aliases) {
    		super(rs);
    		this.decoders = Decoder.decoders(columnNameBinders.entrySet());
    		this.aliases = (Map<Selectable<?>, String>) aliases;
    	}

    	/**
    	 * Constructs an instance that will iterate over the given {@link ResultSet}. It can be changed with {@link #setResultSet(ResultSet)}.
    	 *
    	 * @param rs a ResultSet to wrap into an {@link java.util.Iterator}
    	 * @param columnNameBinders object to extract column names and associated {@link ResultSetReader} to use for <t>ResultSet</t> reading
    	 */
    	public ColumnedRowIterator(ResultSet rs, ParameterBinderIndex<? extends Selectable, ? extends ResultSetReader> columnNameBinders, Map<? extends Selectable<?>, String> aliases) {
    		super(rs);
    		this.decoders = Decoder.decoders(columnNameBinders.all());
    		this.aliases = (Map<Selectable<?>, String>) aliases;
    	}

    	/**
    	 * Implementation that converts current {@link ResultSet} line into a {@link Row} according to {@link ResultSetReader}s given at construction time.
    	 *
    	 * @param rs {@link ResultSet} positioned at line that must be converted
    	 * @return a {@link Row} containing values given by {@link ResultSetReader}s
    	 * @throws SQLException if a read error occurs
    	 * @throws BindingException if a binding doesn't match its ResultSet value
    	 */
    	@Override
    	public ColumnedRow convert(ResultSet rs) throws SQLException {
    		Row toReturn = new Row();
    		for (Decoder columnEntry : decoders) {
    			Selectable<?> column = columnEntry.getColumn();
    			String alias = aliases.get(column);
    			Object columnValue = columnEntry.getReader().get(rs, alias);
    			toReturn.put(alias, columnValue);
    		}
    		return new ColumnedRow() {
    			@Override
    			public <E> E get(Selectable<E> column) {
    				String alias;
    				// we simplify data access for projected columns with an alias (like "count") because they are mainly
    				// (only ?) created by the end user who, without it, must share the operator, which is quite cumbersome
    				if (column instanceof Selectable.SimpleSelectable) {
    					alias = column.getExpression();
    				} else {
    					alias = aliases.get(column);
    					if (alias == null) {
    						// This is more for debugging purpose than for a real production goal, may be removed later
    						throw new IllegalArgumentException("Can't find alias for " + column.getExpression() + " in row entries " + aliases.keySet() + "."
    						+ " Please ensure your declared the column in the select clause.");
    					}
    				}
    				return (E) toReturn.get(alias);
    			}
    		};
    	}

    	/**
    	 * Simple storage of mapping between column name and their {@link ResultSetReader}
    	 */
    	private static class Decoder {

    		private static Iterable<Decoder> decoders(Iterable<? extends Map.Entry<? extends Selectable, ? extends ResultSetReader>> input) {
    			Set<Decoder> result = new LinkedHashSet<>();
    			input.forEach(e -> result.add(new Decoder(e.getKey(), e.getValue())));
    			return result;
    		}

    		private final Selectable<?> column;

    		private final ResultSetReader<?> reader;

    		private Decoder(Selectable<?> column, ResultSetReader<?> reader) {
    			this.column = column;
    			this.reader = reader;
    		}

    		private Selectable<?> getColumn() {
    			return column;
    		}

    		private ResultSetReader<?> getReader() {
    			return reader;
    		}
    	}
    }